/*
 * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.pkcs10;

import java.io.PrintStream;
import java.io.IOException;
import java.math.BigInteger;

import java.security.*;

import java.util.Arrays;
import java.util.Base64;

import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X509Key;
import sun.security.x509.X500Name;
import sun.security.util.SignatureUtil;


/**
 * A PKCS #10 certificate request is created and sent to a Certificate
 * Authority, which then creates an X.509 certificate and returns it to
 * the entity that requested it. A certificate request basically consists
 * of the subject's X.500 name, public key, and optionally some attributes,
 * signed using the corresponding private key.
 *
 * The ASN.1 syntax for a Certification Request is:
 * <pre>
 * CertificationRequest ::= SEQUENCE {
 *    certificationRequestInfo CertificationRequestInfo,
 *    signatureAlgorithm       SignatureAlgorithmIdentifier,
 *    signature                Signature
 *  }
 *
 * SignatureAlgorithmIdentifier ::= AlgorithmIdentifier
 * Signature ::= BIT STRING
 *
 * CertificationRequestInfo ::= SEQUENCE {
 *    version                 Version,
 *    subject                 Name,
 *    subjectPublicKeyInfo    SubjectPublicKeyInfo,
 *    attributes [0] IMPLICIT Attributes
 * }
 * Attributes ::= SET OF Attribute
 * </pre>
 *
 * @author David Brownell
 * @author Amit Kapoor
 * @author Hemma Prafullchandra
 */
public class PKCS10 {
    /**
     * Constructs an unsigned PKCS #10 certificate request.  Before this
     * request may be used, it must be encoded and signed.  Then it
     * must be retrieved in some conventional format (e.g. string).
     *
     * @param publicKey the public key that should be placed
     *          into the certificate generated by the CA.
     */
    public PKCS10(PublicKey publicKey) {
        subjectPublicKeyInfo = publicKey;
        attributeSet = new PKCS10Attributes();
    }

    /**
     * Constructs an unsigned PKCS #10 certificate request.  Before this
     * request may be used, it must be encoded and signed.  Then it
     * must be retrieved in some conventional format (e.g. string).
     *
     * @param publicKey the public key that should be placed
     *          into the certificate generated by the CA.
     * @param attributes additional set of PKCS10 attributes requested
     *          for in the certificate.
     */
    public PKCS10(PublicKey publicKey, PKCS10Attributes attributes) {
        subjectPublicKeyInfo = publicKey;
        attributeSet = attributes;
    }

    /**
     * Parses an encoded, signed PKCS #10 certificate request, verifying
     * the request's signature as it does so.  This constructor would
     * typically be used by a Certificate Authority, from which a new
     * certificate would then be constructed.
     *
     * @param data the DER-encoded PKCS #10 request.
     * @exception IOException for low level errors reading the data
     * @exception SignatureException when the signature is invalid
     * @exception NoSuchAlgorithmException when the signature
     *  algorithm is not supported in this environment
     */
    public PKCS10(byte[] data)
    throws IOException, SignatureException, NoSuchAlgorithmException {
        DerInputStream  in;
        DerValue[]      seq;
        AlgorithmId     id;
        byte[]          sigData;
        Signature       sig;

        encoded = data;

        //
        // Outer sequence:  request, signature algorithm, signature.
        // Parse, and prepare to verify later.
        //
        in = new DerInputStream(data);
        seq = in.getSequence(3);

        if (seq.length != 3)
            throw new IllegalArgumentException("not a PKCS #10 request");

        data = seq[0].toByteArray();            // reusing this variable
        id = AlgorithmId.parse(seq[1]);
        sigData = seq[2].getBitString();

        //
        // Inner sequence:  version, name, key, attributes
        //
        BigInteger      serial;

        serial = seq[0].data.getBigInteger();
        if (!serial.equals(BigInteger.ZERO))
            throw new IllegalArgumentException("not PKCS #10 v1");

        subject = new X500Name(seq[0].data);
        subjectPublicKeyInfo = X509Key.parse(seq[0].data.getDerValue());

        // Cope with a somewhat common illegal PKCS #10 format
        if (seq[0].data.available() != 0)
            attributeSet = new PKCS10Attributes(seq[0].data);
        else
            attributeSet = new PKCS10Attributes();

        if (seq[0].data.available() != 0)
            throw new IllegalArgumentException("illegal PKCS #10 data");

        //
        // OK, we parsed it all ... validate the signature using the
        // key and signature algorithm we found.
        //
        try {
            sigAlg = id.getName();
            sig = Signature.getInstance(sigAlg);
            SignatureUtil.initVerifyWithParam(sig, subjectPublicKeyInfo,
                SignatureUtil.getParamSpec(sigAlg, id.getParameters()));

            sig.update(data);
            if (!sig.verify(sigData)) {
                throw new SignatureException("Invalid PKCS #10 signature");
            }
        } catch (InvalidKeyException e) {
            throw new SignatureException("Invalid key", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new SignatureException("Invalid signature parameters", e);
        } catch (ProviderException e) {
            throw new SignatureException("Error parsing signature parameters",
                e.getCause());
        }
    }

    /**
     * Create the signed certificate request.  This will later be
     * retrieved in either string or binary format.
     *
     * @param subject identifies the signer (by X.500 name).
     * @param key private key to use.
     * @param algorithm signing algorithm to use.
     * @exception IOException on errors.
     * @exception SignatureException on signature handling errors.
     * @exception NoSuchAlgorithmException algorithm is not recognized
     * @exception InvalidKeyException key has a problem
     */
    public void encodeAndSign(X500Name subject, PrivateKey key, String algorithm)
            throws SignatureException,
                    NoSuchAlgorithmException, InvalidKeyException {

        DerOutputStream out, scratch;
        byte[]          certificateRequestInfo;
        byte[]          sig;

        if (encoded != null) {
            throw new SignatureException("request is already signed");
        }

        Signature signature = SignatureUtil.fromKey(
                algorithm, key, (Provider)null);

        this.subject = subject;

        /*
         * Encode cert request info, wrap in a sequence for signing
         */
        scratch = new DerOutputStream();
        scratch.putInteger(BigInteger.ZERO);            // PKCS #10 v1.0
        subject.encode(scratch);                        // X.500 name
        scratch.writeBytes(subjectPublicKeyInfo.getEncoded()); // public key
        attributeSet.encode(scratch);

        out = new DerOutputStream();
        out.write(DerValue.tag_Sequence, scratch);      // wrap it!
        certificateRequestInfo = out.toByteArray();
        scratch = out;

        /*
         * Sign it ...
         */
        signature.update(certificateRequestInfo, 0,
                certificateRequestInfo.length);
        sig = signature.sign();
        sigAlg = signature.getAlgorithm();

        /*
         * Build guts of SIGNED macro
         */
        AlgorithmId algId = SignatureUtil.fromSignature(signature, key);

        algId.encode(scratch);     // sig algorithm
        scratch.putBitString(sig);                      // sig

        /*
         * Wrap those guts in a sequence
         */
        out = new DerOutputStream();
        out.write(DerValue.tag_Sequence, scratch);
        encoded = out.toByteArray();
    }

    /**
     * Returns the subject's name.
     */
    public X500Name getSubjectName() { return subject; }

    /**
     * Returns the subject's public key.
     */
    public PublicKey getSubjectPublicKeyInfo()
        { return subjectPublicKeyInfo; }

    /**
     * Returns the signature algorithm.
     */
    public String getSigAlg() { return sigAlg; }

    /**
     * Returns the additional attributes requested.
     */
    public PKCS10Attributes getAttributes()
        { return attributeSet; }

    /**
     * Returns the encoded and signed certificate request as a
     * DER-encoded byte array.
     *
     * @return the certificate request, or null if encodeAndSign()
     *          has not yet been called.
     */
    public byte[] getEncoded() {
        if (encoded != null)
            return encoded.clone();
        else
            return null;
    }

    /**
     * Prints an E-Mailable version of the certificate request on the print
     * stream passed.  The format is a common base64 encoded one, supported
     * by most Certificate Authorities because Netscape web servers have
     * used this for some time.  Some certificate authorities expect some
     * more information, in particular contact information for the web
     * server administrator.
     *
     * @param out the print stream where the certificate request
     *  will be printed.
     * @exception IOException when an output operation failed
     * @exception SignatureException when the certificate request was
     *  not yet signed.
     */
    public void print(PrintStream out)
    throws IOException, SignatureException {
        if (encoded == null)
            throw new SignatureException("Cert request was not signed");


        byte[] CRLF = new byte[] {'\r', '\n'};
        out.print("-----BEGIN NEW CERTIFICATE REQUEST-----");
        out.print("\r\n");
        out.print(Base64.getMimeEncoder(64, CRLF).encodeToString(encoded));
        out.print("\r\n");
        out.print("-----END NEW CERTIFICATE REQUEST-----");
        out.print("\r\n");
    }

    /**
     * Provides a short description of this request.
     */
    public String toString() {
        return "[PKCS #10 certificate request:\n"
            + subjectPublicKeyInfo.toString()
            + " subject: <" + subject + ">" + "\n"
            + " attributes: " + attributeSet.toString()
            + "\n]";
    }

    /**
     * Compares this object for equality with the specified
     * object. If the <code>obj</code> object is an
     * <code>instanceof</code> <code>PKCS10</code>, then
     * its encoded form is retrieved and compared with the
     * encoded form of this certificate request.
     *
     * @param obj the object to test for equality with this object.
     * @return true iff the encoded forms of the two certificate
     * requests match, false otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof PKCS10 other))
            return false;
        if (encoded == null) // not signed yet
            return false;
        byte[] otherEncoded = other.getEncoded();
        if (otherEncoded == null)
            return false;

        return Arrays.equals(encoded, otherEncoded);
    }

    /**
     * {@return the hashcode value for this certificate request from its
     * encoded form}
     */
    @Override
    public int hashCode() {
        return Arrays.hashCode(encoded);
    }

    private X500Name                subject;
    private final PublicKey         subjectPublicKeyInfo;
    private String                  sigAlg;
    private final PKCS10Attributes  attributeSet;
    private byte[]                  encoded;        // signed
}
